/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/sksg/include/SkSGMerge.h"

#include "include/core/SkCanvas.h"
#include "include/pathops/SkPathOps.h"
#include "src/core/SkPathPriv.h"

namespace sksg {
Merge::Merge(std::vector<Rec> &&recs) : fRecs(std::move(recs))
{
    for (const auto &rec : fRecs) {
        this->observeInval(rec.fGeo);
    }
}

Merge::~Merge()
{
    for (const auto &rec : fRecs) {
        this->unobserveInval(rec.fGeo);
    }
}

void Merge::onClip(SkCanvas *canvas, bool antiAlias) const
{
    canvas->clipPath(fMerged, SkClipOp::kIntersect, antiAlias);
}

void Merge::onDraw(SkCanvas *canvas, const SkPaint &paint) const
{
    canvas->drawPath(fMerged, paint);
}

bool Merge::onContains(const SkPoint &p) const
{
    return fMerged.contains(p.x(), p.y());
}

SkPath Merge::onAsPath() const
{
    return fMerged;
}

static SkPathOp mode_to_op(Merge::Mode mode)
{
    switch (mode) {
        case Merge::Mode::kUnion:
            return kUnion_SkPathOp;
        case Merge::Mode::kIntersect:
            return kIntersect_SkPathOp;
        case Merge::Mode::kDifference:
            return kDifference_SkPathOp;
        case Merge::Mode::kReverseDifference:
            return kReverseDifference_SkPathOp;
        case Merge::Mode::kXOR:
            return kXOR_SkPathOp;
        default:
            break;
    }

    return kUnion_SkPathOp;
}

SkRect Merge::onRevalidate(InvalidationController *ic, const SkMatrix &ctm)
{
    SkASSERT(this->hasInval());

    SkOpBuilder builder;

    fMerged.reset();
    bool in_builder = false;

    auto append = [&](const SkPath &path) {
        if (in_builder) {
            builder.resolve(&fMerged);
            in_builder = false;
        }

        if (fMerged.isEmpty()) {
            // First merge path determines the fill type.
            fMerged = path;
        } else {
            fMerged.addPath(path);
        }
    };

    for (const auto &rec : fRecs) {
        rec.fGeo->revalidate(ic, ctm);

        if (rec.fMode == Mode::kMerge) {
            // Merge (append) is not supported by SkOpBuidler.
            append(rec.fGeo->asPath());
            continue;
        }

        if (!in_builder) {
            builder.add(fMerged, kUnion_SkPathOp);
            in_builder = true;
        }

        builder.add(rec.fGeo->asPath(), mode_to_op(rec.fMode));
    }

    if (in_builder) {
        builder.resolve(&fMerged);
    }

    SkPathPriv::ShrinkToFit(&fMerged);

    return fMerged.computeTightBounds();
}
} // namespace sksg
